#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
Simplified BSD License (BSD)
THN HTTP Stack Library
Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 04-Nov-12 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;

using Thn;
using Thn.IO;
#endregion

namespace Thn.Net.Http
{
    /// <summary>
    /// A simple to use implementation of <see cref="IHttpRequest"/>.
    /// </summary>
#if DEBUG || TESTING
    public
#else
    public
#endif
 class HttpRequest : HttpMessageBase, IHttpRequest
    {
        #region Fields
        #endregion

        #region Properties

        #region Headers
        /// <summary>
        /// Gets or sets reference to the headers section.
        /// </summary>
        public HttpHeaders Headers
        {
            get { return mHeaders; }
            set { mHeaders = value; }
        }
        #endregion

        #region Body
        /// <summary>
        /// Gets reference to the body section.
        /// </summary>
        public IHttpBody Body
        {
            get { return mBody; }
        }
        #endregion

        #region Method
        private HttpRequestMethod mMethod = HttpRequestMethod.Get;
        /// <summary>
        /// Gets or sets the method of request.
        /// <para>Default is GET.</para>
        /// </summary>
        public HttpRequestMethod Method
        {
            get { return mMethod; }
            set
            {
                mMethod = value;
                if (value != HttpRequestMethod.Unknown)
                {
                    mMethodName = string.Format("{0}", mMethod).ToUpper();
                }
                #region Defensive tracing
                else
                {
                    //don't customize the method name for unknown method.
                }
                #endregion
            }
        }
        #endregion

        #region Method Name
        private string mMethodName = "GET";
        /// <summary>
        /// Gets or sets the method of request as a string value.
        /// </summary>
        public string MethodName
        {
            get { return mMethodName; }
            set
            {
                mMethodName = value;
                mMethod = HttpRequestMethodFromText.Get(mMethodName);
            }
        }
        #endregion

        #region URL
        private Uri mUrl;
        /// <summary>
        /// Gets or sets the URL of this request.
        /// </summary>
        public Uri Url
        {
            get
            {
                //lazy building of URI
                if (mUrl == null)
                {
                    string host = this.Host;
                    int port = this.Port;

                    //host and port portion
                    string txt = "http://" + host;
                    if (port != 80) txt += ":" + port.ToString();                    

                    //path
                    var path = this.Path;
                    if (!path.StartsWith("/")) txt += "/" + path;
                    else txt += path;

                    mUrl = new Uri(txt);
                }
                return mUrl;
            }
            set
            {
                mUrl = value;

                //translate URI to path
                //this.Path = mUri.PathAndQuery;                
                this.Path = mUrl.LocalPath + mUrl.Query;
                this.Host = mUrl.Host;
            }
        }
        #endregion

        #region Path
        private string mPath;
        /// <summary>
        /// Gets or sets the raw path of the requested resource.
        /// </summary>
        public string Path
        {
            get { return mPath; }
            set
            {
                mPath = value;
                mQuery = null;
            }
        }
        #endregion

        #region Version
        private string mVersion = HttpVersionValues.Version_1_1;
        /// <summary>
        /// Gets or sets the HTTP version this request uses.
        /// <para>Default is version 1.1</para>
        /// </summary>
        public string Version
        {
            get { return mVersion; }
            set { mVersion = value; }
        }
        #endregion

        #region Host
        /// <summary>
        /// Gets or sets the host value.
        /// </summary>
        public string Host
        {
            get
            {
                string result = Headers[HttpRequestHeaderNames.Host];
                if (string.IsNullOrEmpty(result)) result = mUrl.Host;
                #region Defensive tracing
                else
                {
                    //manually assigned
                }
                #endregion

                return result;
                //return Headers[HttpRequestHeaderNames.Host];
            }
            set { Headers[HttpRequestHeaderNames.Host] = value; }
        }
        #endregion

        #region Hostname
        /// <summary>
        /// Gets DNS-safe hostname of the request.
        /// </summary>
        string IHttpRequest.Hostname
        {
            get
            {
                string result; ;

                if (mUrl != null) result = mUrl.DnsSafeHost;
                else result = this.Host;

                return result;
            }
        }
        #endregion

        #region User Agent
        /// <summary>
        /// Gets or sets the User-Agent header.
        /// </summary>
        public string UserAgent
        {
            get
            {
                return Headers[HttpRequestHeaderNames.User_Agent];
            }
            set { Headers[HttpRequestHeaderNames.User_Agent] = value; }
        }
        #endregion

        #region Keep Alive
        /// <summary>
        /// Gets or sets the Keep-Alive header.
        /// </summary>
        public bool KeepAlive
        {
            get
            {
                bool result = false;
                if (Headers.ContainsKey(HttpRequestHeaderNames.Connection))
                {
                    string value = Headers[HttpRequestHeaderNames.Connection].ToLower();
                    if (value == HttpConnectionValues.KeepAlive) result = true;
                }
                else result = false;
                return result;
            }
            set
            {
                Headers[HttpRequestHeaderNames.Connection] = value ? HttpConnectionValues.KeepAlive : HttpConnectionValues.Close;
            }
        }
        #endregion

        #region Port
        /// <summary>
        /// Gets port of host.
        /// </summary>
        public int Port
        {
            get
            {
                int result = 80;
                if (mUrl != null) result = mUrl.Port;
                #region Defensive tracing
                else
                {
#if DEBUG || TESTING
                    Log.Warning("Cannot determine port");
#endif
                }
                #endregion

                return result;
            }
        }
        #endregion

        #region Query
        private UrlQuery mQuery;
        /// <summary>
        /// Gets reference to the URL queries.
        /// <para>Note: upon first access, the query shall be parsed and built from current path.</para>
        /// <para>This property is provided as a handy feature for consumer codes. For server code,
        /// it is recommended to use the <see cref="UrlParser"/> mannually for more controls.</para>
        /// </summary>
        public virtual UrlQuery Query
        {
            get
            {
                //lazy parsing
                if (mQuery == null)
                {
                    var parser = new UrlParser();
                    parser.Parse(mPath);
                    mQuery = parser.Query;
                }
                return mQuery;
            }
        }
        #endregion

        #region Form Data
        private FormData mFormData;
        /// <summary>
        /// Gets reference to the posted form data. (Only available when method is POST)
        /// <para>Note: upon first access, the form will be parsed from the body.</para>
        /// <para>This property is provided as a handy feature for consumer codes. For server code,
        /// it is recommended to use an appropriate form data parser mannually for more controls.</para>
        /// </summary>
        public virtual FormData FormData
        {
            get
            {
                //lazy parsing
                if (mFormData == null)
                {
                    if (mMethod == HttpRequestMethod.Post)
                    {
                        #region POST -> parse

                        var contentType = this.Headers[HttpRequestHeaderNames.Content_Type];
                        if (contentType == "application/x-www-form-urlencoded"
                            || contentType == "application/x-url-encoded"
                            )
                        {
                            #region URL Encoded

                            UrlEncodedFormDataParser up = new UrlEncodedFormDataParser();
                            up.Parse(Body.Reader);
                            mFormData = up.Form;

                            #endregion
                        }
                        else if (contentType == "")
                        {
                            #region Multi-part
                            throw new NotSupportedException();
                            #endregion
                        }
                        else
                        {
                            #region Unsupported -> return empty

                            mFormData = new FormData();

                            #endregion
                        }
                        #endregion
                    }
                    else
                    {
                        #region Only POST is supported, return empty for others
                        
                        mFormData = new FormData();

                        #endregion
                    }
                }

                return mFormData;
            }
        }
        #endregion
        
        #region Indexer
        /// <summary>
        /// Gets the value of a parameter based on its name.
        /// <para>Note: this is a handy feature for looking up both Query and FormData.
        /// For server programming, it is recommended to manually use <see cref="UrlParser"/>
        /// and <see cref="FormDataParser"/> instead.</para>
        /// </summary>
        /// <param name="paramaterName">Name of the parameter to look up for.</param>
        /// <returns>The parameter's value if exists, false otherwise.</returns>
        public string this[string paramaterName]
        {
            get
            {
                string result = null;

                if (mMethod == HttpRequestMethod.Post)
                {
                    #region POST -> prefer FORM DATA

                    //lookup from FORM DATA first
                    result = FormData[paramaterName];

                    //did not find -> look up from QUERY
                    if (string.IsNullOrEmpty(result))
                    {
                        result = Query[paramaterName];
                    }

                    #endregion
                }
                else
                {
                    #region Others -> prefer QUERY

                    //lookup from QUERY first
                    result = Query[paramaterName];

                    //did not find -> look up from FORM DATA
                    if (string.IsNullOrEmpty(result))
                    {
                        result = FormData[paramaterName];
                    }

                    #endregion
                }

                return result;
            }
        }
        #endregion

        #endregion

        #region On Serialize
        /// <summary>
        /// Write the content of this message to the provided writer.
        /// </summary>
        /// <param name="writer">The write responsible for writing to underlying context.</param>
        protected override void OnSerialize(IContextWriter writer)
        {
            //ensure HOST header is always added to conform to HTTP 1.1
            this.Host = Host;

            if (mBody.HasData)
            {
                #region Has Data

                long bodyLength = OnGetBodyLength();

                //write request line
                writer.Write(string.Format("{0} {1} {2}\r\n", mMethodName, mUrl.LocalPath + mUrl.Query, mVersion));

                //set content-length
                mHeaders[HttpRequestHeaderNames.Content_Length] = bodyLength.ToString();

                //write header
                mHeaders.Serialize(writer);

                //add blank line
                writer.WriteLine("");

                //write content
                HttpBody body = (HttpBody)mBody;
                IHasByteArray b = (IHasByteArray)body;
                writer.WriteBytes(b.Buffer, b.StartOffset, (int)body.Length);

                #endregion
            }
            else
            {
                #region No data

                //write request line                
                if (!string.IsNullOrEmpty(mPath))
                {
                    writer.Write(string.Format("{0} {1} {2}\r\n", mMethodName, mPath, mVersion));
                }
                else
                {
                    writer.Write(string.Format("{0} {1} {2}\r\n", mMethodName, mUrl.LocalPath + mUrl.Query, mVersion));
                }

                //write header
                mHeaders.Serialize(writer);

                #endregion
            }
        }
        #endregion

        

        #region Constructors
        /// <summary>
        /// Default constructor.
        /// </summary>
        public HttpRequest()
            : base()
        { }
        #endregion
    }
}
